package com.yayohos.locationmanager;

import com.yayohos.locationmanager.configuration.LocationConfiguration;
import com.yayohos.locationmanager.constants.FailType;
import com.yayohos.locationmanager.constants.ProcessType;
import com.yayohos.locationmanager.helper.LogUtils;
import com.yayohos.locationmanager.listener.LocationListener;
import com.yayohos.locationmanager.listener.PermissionListener;
import com.yayohos.locationmanager.providers.locationprovider.DefaultLocationProvider;
import com.yayohos.locationmanager.providers.locationprovider.LocationProvider;
import com.yayohos.locationmanager.providers.permissionprovider.PermissionProvider;
import com.yayohos.locationmanager.view.ContextProcessor;

import ohos.aafwk.ability.Ability;
import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.content.Intent;
import ohos.app.Context;

/**
 * LocationManager
 *
 * @date 2021/5/26
 */
public final class LocationManager implements PermissionListener {
    private final LocationListener listener;
    private final LocationConfiguration configuration;
    private final LocationProvider activeProvider;
    private final PermissionProvider permissionProvider;

    private LocationManager(Builder builder) {
        this.listener = builder.listener;
        this.configuration = builder.configuration;
        this.activeProvider = builder.activeProvider;
        this.permissionProvider = getConfiguration().permissionConfiguration().permissionProvider();
        this.permissionProvider.setContextProcessor(builder.contextProcessor);
        this.permissionProvider.setPermissionListener(this);
    }

    /**
     * enableLog
     *
     * @param enable Library tries to log as much as possible in order to make it transparent to see
     *               what is actually going on
     *               under the hood. You can enable it for debug purposes, but do not forget to disable on production.
     *               <p>
     *               Log is disabled as default.
     */
    public static void enableLog(boolean enable) {
        LogUtils.enable(enable);
    }

    /**
     * The only method you need to call to trigger getting location process
     */
    public void get() {
        askForPermission();
    }

    /**
     * Returns configuration object which is defined to this manager
     *
     * @return LocationConfiguration
     */
    public LocationConfiguration getConfiguration() {
        return configuration;
    }

    /**
     * onForeground
     *
     * @param intent Intent
     */
    public void onForeground(Intent intent) {
        activeProvider.onResume();
        LogUtils.logI("onForeground");
    }

    /**
     * ability 生命周期 onBackground()处理 activeProvider
     */
    public void onBackground() {
        activeProvider.onPause();
    }

    /**
     * ability 生命周期 onStop()处理 activeProvider
     */
    public void onStop() {
        activeProvider.onDestroy();
    }

    /**
     * This is required to check when user handles with error, or enables GPS...
     *
     * @param requestCode requestCode
     * @param resultCode  resultCode
     * @param data        data
     */
    public void onAbilityResult(int requestCode, int resultCode, Intent data) {
        activeProvider.onAbilityResult(requestCode, resultCode, data);
    }


    /**
     * Provide requestPermissionResult to manager so the it can handle RuntimePermission
     *
     * @param requestCode  requestCode
     * @param permissions  permissions
     * @param grantResults grantResults
     */
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        permissionProvider.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }

    /**
     * 获取权限
     */
    void askForPermission() {
        if (permissionProvider.hasPermission()) {
            LogUtils.logI("hasPermission");
            permissionGranted(permissionProvider.hasPermission());
        } else {
            if (listener != null) {
                listener.onProcessTypeChanged(ProcessType.ASKING_PERMISSIONS);
            }

            if (permissionProvider.requestPermissions()) {
                LogUtils.logI("Waiting until we receive any callback from PermissionProvider...");
            } else {
                LogUtils.logI("Couldn't get permission, Abort!");
                failed(FailType.PERMISSION_DENIED);
            }
        }
    }

    private void permissionGranted(boolean alreadyHadPermission) {
        LogUtils.logI("We got permission!");

        if (listener != null) {
            listener.onPermissionGranted(alreadyHadPermission);
        }
        activeProvider.get();
    }

    private void failed(int type) {
        if (listener != null) {
            listener.onLocationFailed(type);
        }
    }

    @Override
    public void onPermissionsGranted() {
        permissionGranted(true);
    }

    @Override
    public void onPermissionsDenied() {
        failed(FailType.PERMISSION_DENIED);
    }

    /**
     * 构造器
     */
    public static class Builder {
        private ContextProcessor contextProcessor;
        private LocationListener listener;
        private LocationConfiguration configuration;
        private LocationProvider activeProvider;

        /**
         * Builder object to create LocationManager
         *
         * @param contextProcessor holds the address of the context,which this manager will run on
         * @throws IllegalStateException set a contextProcessor to LocationManager
         */
        public Builder(ContextProcessor contextProcessor) {
            if (contextProcessor == null) {
                throw new IllegalStateException("You must set a contextProcessor to LocationManager.");
            }
            this.contextProcessor = contextProcessor;
        }

        /**
         * Builder object to create LocationManager
         *
         * @param context MUST be an application context
         * @throws IllegalStateException You must set a context to ContextProcessor
         */
        public Builder(Context context) {
            if (context == null) {
                throw new IllegalStateException("You must set a context to ContextProcessor.");
            }
            this.contextProcessor = new ContextProcessor(context);
        }

        /**
         * Ability is required in order to ask for permission, GPS enable dialog, Rationale dialog,
         *
         * @param ability will be kept as weakReference
         * @return Builder
         */
        public Builder ability(Ability ability) {
            this.contextProcessor.setAbility(ability);
            return this;
        }


        /**
         * AbilitySlice is required in order to ask for permission, GPS enable dialog, Rationale dialog,
         *
         * @param abilitySlice will be kept as weakReference
         * @return Builder
         */
        public Builder abilitySlice(AbilitySlice abilitySlice) {
            this.contextProcessor.setAbilitySlice(abilitySlice);
            return this;
        }


        /**
         * Configuration object in order to take decisions accordingly while trying to retrieve location
         *
         * @param locationConfiguration locationConfiguration
         * @return Builder Builder
         * @throws IllegalStateException a locationConfiguration to locationManager
         */
        public Builder configuration(LocationConfiguration locationConfiguration) {
            if (locationConfiguration == null) {
                throw new IllegalStateException("You must set a locationConfiguration to locationManager.");
            }
            this.configuration = locationConfiguration;
            return this;
        }

        /**
         * Instead of using {@linkplain DefaultLocationProvider} you can create your own,
         * and set it to manager so it will use given one.
         *
         * @param provider LocationProvider
         * @return Builder
         */
        public Builder locationProvider(LocationProvider provider) {
            this.activeProvider = provider;
            return this;
        }

        /**
         * Specify a LocationListener to receive location when it is available,
         * or get knowledge of any other steps in process
         *
         * @param listener LocationListener
         * @return Builder 构造器
         */
        public Builder notify(LocationListener listener) {
            this.listener = listener;
            return this;
        }

        /**
         * 构建LocationManager
         *
         * @return LocationManager
         */
        public LocationManager build() {
            if (contextProcessor == null) {
                throw new IllegalStateException("You must set a context to LocationManager.");
            }
            if (configuration == null) {
                throw new IllegalStateException("You must set a configuration object.");
            }
            if (activeProvider == null) {
                locationProvider(new DefaultLocationProvider());
            }
            this.activeProvider.configure(contextProcessor, configuration, listener);
            return new LocationManager(this);
        }

    }


}
